<template>
  <div class="file-uploader">
    <el-upload
      class="upload-demo"
      :action="uploadUrl"
      :before-upload="beforeUpload"
      :on-success="onSuccess"
      :on-error="onError"
      :auto-upload="true"
      :file-list="fileList"
      ref="upload"
      :multiple="false"
    >
      <el-button slot="trigger" size="large" type="primary">选取文件</el-button>
      <div slot="tip" class="el-upload__tip">
        支持单个大文件分片上传
      </div>
    </el-upload>
    <el-progress v-if="uploading" :percentage="progress" :status="uploadStatus" style="margin-top: 20px;"></el-progress>
    <div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
  </div>
</template>

<script>
import axios from 'axios';
import SparkMD5 from 'spark-md5';

export default {
  name: 'FileUploader',
  props: {
    baseUrl: {
      type: String,
      default: '/api'
    },
    chunkSize: {
      type: Number,
      default: 1024 * 1024 * 4 // 默认4MB，小于后端5MB的限制
    },
    maxRetries: {
      type: Number,
      default: 3
    },
    concurrencyLimit: {
      type: Number,
      default: 3 // 默认并发数
    }
  },
  data() {
    return {
      uploadUrl: this.baseUrl + '/upload',
      fileList: [],
      uploading: false,
      progress: 0,
      uploadStatus: 'success',
      errorMessage: '',
      retryCount: 0,
      lastProgressMessageTime: 0 // 用于控制消息节流
    };
  },
  
  beforeDestroy() {
    },
  watch: {
    baseUrl(newVal) {
      this.uploadUrl = newVal + '/upload';
    }
  },
  methods: {
    beforeUpload(file) {
      this.fileList = [file];
      this.errorMessage = '';
      this.retryCount = 0;
      this.submitUpload();
      return false; // 返回false以使用我们自定义的上传逻辑
    },
    
    async submitUpload() {
      if (!this.fileList.length) {
        this.showMessage('请先选择文件', 'warning');
        return;
      }
      
      const file = this.fileList[0];
      const totalChunks = Math.ceil(file.size / this.chunkSize);
      const fileHash = await this.calculateFileHash(file);
      let uploadedChunks = 0;
      
      this.uploading = true;
      this.progress = 0;
      this.uploadStatus = 'success';
      this.errorMessage = '';
      
      try {
        // 检查是否已上传过或有部分上传
        this.showMessage('正在检查文件状态...', 'info');
        const checkResponse = await this.checkFile(fileHash, file.name);
        
        if (checkResponse.data.exists) {
          this.showMessage('文件已存在，无需重复上传', 'success');
          this.uploading = false;
          this.progress = 100;
          this.$emit('upload-success', { fileName: file.name, fileHash });
          return;
        }
        
        // 获取已上传的分片索引
        const uploadedChunksList = checkResponse.data.uploadedChunks || [];
        uploadedChunks = uploadedChunksList.length;
        this.progress = Math.round((uploadedChunks * 100) / totalChunks);
        
        if (uploadedChunks > 0) {
          this.showMessage(`检测到已上传 ${uploadedChunks} 个分片，继续上传剩余部分`, 'info');
        }
        
        // 上传分片
        this.showMessage(`开始上传文件：${file.name}`, 'info');
        
        // 并发控制上传分片
        await this.concurrentUpload(file, uploadedChunksList, totalChunks, fileHash);
        
        // 合并分片
        this.showMessage('正在合并文件分片...', 'info');
        await this.mergeChunks(fileHash, file.name, totalChunks);
        
        this.showMessage('文件上传成功', 'success');
        this.uploading = false;
        this.progress = 100;
        this.$emit('upload-success', { fileName: file.name, fileHash });
        
      } catch (error) {
        this.errorMessage = '文件上传失败：' + (error.message || '未知错误');
        this.uploadStatus = 'exception';
        this.uploading = false;
        this.$emit('upload-error', error);
      }
    },
    
    // 移除重复的方法定义，只保留带有进度回调的版本
    
    async checkFile(fileHash, fileName) {
      return axios.get(`${this.baseUrl}/upload/check`, {
        params: {
          fileHash,
          fileName
        }
      });
    },
    
    async mergeChunks(fileHash, fileName, totalChunks) {
      return axios.post(`${this.baseUrl}/upload/merge`, {
        fileHash,
        fileName,
        chunks: totalChunks
      });
    },
    

    
    // 同步计算文件哈希（备用方案）
    calculateFileHashSync(file) {
      return new Promise((resolve) => {
        const spark = new SparkMD5.ArrayBuffer();
        const chunkSize = 1024 * 1024 * 2; // 2MB chunk for faster reading
        const fileReader = new FileReader();
        let currentChunk = 0;
        
        fileReader.onload = (e) => {
          spark.append(e.target.result);
          currentChunk++;
          
          // 更新MD5计算进度，但减少进度提示频率
          const progress = Math.round((currentChunk * chunkSize * 100) / file.size);
          // 只在关键进度点显示提示
          if (progress <= 100 && (progress % 20 === 0 || progress === 100)) {
            this.showMessage(`MD5计算进度: ${Math.min(progress, 100)}%`, 'info');
          }
          
          if (currentChunk < Math.ceil(file.size / chunkSize)) {
            loadNextChunk();
          } else {
            const md5Hash = spark.end();
            this.showMessage(`文件MD5计算完成: ${md5Hash}`, 'info');
            resolve(md5Hash);
          }
        };
        
        fileReader.onerror = (error) => {
          this.showMessage('MD5计算失败: ' + error, 'error');
          // 失败时回退到使用文件名+大小+修改时间作为hash
          const fallbackHash = file.name + '_' + file.size + '_' + file.lastModified;
          resolve(fallbackHash);
        };
        
        function loadNextChunk() {
          const start = currentChunk * chunkSize;
          const end = Math.min(start + chunkSize, file.size);
          fileReader.readAsArrayBuffer(file.slice(start, end));
        }
        
        loadNextChunk();
      });
    },
    

    
    // 统一的哈希计算入口
    async calculateFileHash(file) {
      this.showMessage('正在计算文件哈希值...', 'info');
      
      try {
        // 直接使用主线程计算哈希
        return await this.calculateFileHashSync(file);
      } catch (error) {
        this.showMessage('哈希计算失败: ' + error.message, 'error');
        // 失败时回退到使用文件名+大小+修改时间作为hash
        const fallbackHash = file.name + '_' + file.size + '_' + file.lastModified;
        this.showMessage('使用备用哈希计算方式', 'info');
        return fallbackHash;
      }
    },
    
    // 优化消息提示，添加节流控制，避免过多提示
    showMessage(message, type = 'info', throttle = true) {
      // 对于info类型的进度消息进行节流，避免过多提示
      if (type === 'info' && throttle) {
        // 只有当消息包含进度百分比且距离上次提示超过2秒时才显示
        if (message.includes('%') && Date.now() - this.lastProgressMessageTime < 2000) {
          console.log(`[INFO] ${message}`);
          return;
        }
        this.lastProgressMessageTime = Date.now();
      }
      
      if (this.$message) {
        // 避免消息堆积，关闭当前所有消息后显示新消息
        this.$message.closeAll();
        this.$message[type](message);
      } else {
        console.log(`[${type.toUpperCase()}] ${message}`);
      }
    },
    
    onSuccess(response) {
      // 这个方法主要是为了兼容el-upload组件的API
      console.log('Upload success:', response);
    },
    
    onError(error) {
      // 这个方法主要是为了兼容el-upload组件的API
      console.error('Upload error:', error);
    },
    
    // 并发控制上传
    async concurrentUpload(file, uploadedChunksList, totalChunks, fileHash) {
      const chunksToUpload = [];
      
      // 收集需要上传的分片索引
      for (let i = 0; i < totalChunks; i++) {
        if (!uploadedChunksList.includes(i)) {
          chunksToUpload.push(i);
        }
      }
      
      if (chunksToUpload.length === 0) {
        this.showMessage('所有分片已上传，无需重复上传', 'info');
        return;
      }
      
      this.showMessage(`共有 ${chunksToUpload.length} 个分片需要上传，并发数: ${this.concurrencyLimit}`, 'info');
      
      // 分片上传进度跟踪
      const chunkProgresses = {};
      let activeUploads = 0;
      let currentIndex = 0;
      let uploadedCount = 0;
      
      // 使用Promise控制并发
      return new Promise((resolve, reject) => {
        const uploadNextChunk = async () => {
          if (currentIndex >= chunksToUpload.length) {
            // 所有分片都已处理
            if (activeUploads === 0) {
              resolve();
            }
            return;
          }
          
          // 控制并发数
          while (activeUploads >= this.concurrencyLimit) {
            await new Promise(resolve => setTimeout(resolve, 100));
          }
          
          const chunkIndex = chunksToUpload[currentIndex];
          currentIndex++;
          
          activeUploads++;
          
          try {
            await this.uploadChunk(file, chunkIndex, totalChunks, fileHash, (progress) => {
              // 更新单个分片的进度
              chunkProgresses[chunkIndex] = progress;
              
              // 计算整体进度
              let totalProgress = 0;
              let count = 0;
              
              for (let i = 0; i < totalChunks; i++) {
                if (uploadedChunksList.includes(i)) {
                  totalProgress += 100;
                  count++;
                } else if (chunkProgresses[i] !== undefined) {
                  totalProgress += chunkProgresses[i];
                  count++;
                }
              }
              
              if (count > 0) {
                this.progress = Math.round(totalProgress / totalChunks);
              }
            });
            
            uploadedCount++;
            // 减少分片完成提示，只在关键进度点显示
            if (uploadedCount === chunksToUpload.length || uploadedCount % 10 === 0) {
              this.showMessage(`已上传 ${uploadedCount}/${chunksToUpload.length} 个分片`, 'info');
            }
          } catch (error) {
            reject(error);
            return;
          } finally {
            activeUploads--;
            uploadNextChunk();
          }
        };
        
        // 启动并发上传
        for (let i = 0; i < Math.min(this.concurrencyLimit, chunksToUpload.length); i++) {
          uploadNextChunk();
        }
      });
    },
    
    // 上传单个分片，支持重试和进度回调
    async uploadChunk(file, chunkIndex, totalChunks, fileHash, progressCallback = null) {
      let retries = 0;
      
      while (retries <= this.maxRetries) {
        try {
          await this._doUploadChunk(file, chunkIndex, totalChunks, fileHash, progressCallback);
          return; // 上传成功，跳出循环
        } catch (error) {
          retries++;
          if (retries > this.maxRetries) {
            throw new Error(`分片 ${chunkIndex} 上传失败，已重试 ${this.maxRetries} 次`);
          }
          // 指数退避重试
          const delay = Math.pow(2, retries - 1) * 1000;
          await new Promise(resolve => setTimeout(resolve, delay));
          this.showMessage(`重试上传分片 ${chunkIndex} (${retries}/${this.maxRetries})`, 'warning');
        }
      }
    },
    
    // 实际的分片上传实现
    async _doUploadChunk(file, chunkIndex, totalChunks, fileHash, progressCallback) {
      const start = chunkIndex * this.chunkSize;
      const end = Math.min(start + this.chunkSize, file.size);
      const chunk = file.slice(start, end);
      
      const formData = new FormData();
      formData.append('file', chunk);
      formData.append('chunk', chunkIndex);
      formData.append('chunks', totalChunks);
      formData.append('fileHash', fileHash);
      formData.append('fileName', file.name);
      
      return axios.post(`${this.baseUrl}/upload/chunk`, formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        onUploadProgress: (progressEvent) => {
          if (progressEvent.total) {
            const chunkProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
            
            if (progressCallback) {
              progressCallback(chunkProgress);
            }
          }
        }
      });
    },
    
    // 提供外部调用的方法
    reset() {
      this.fileList = [];
      this.progress = 0;
      this.uploading = false;
      this.errorMessage = '';
    }
  }
};
</script>

<style scoped>
.file-uploader {
  width: 100%;
}

.error-message {
  color: #f56c6c;
  margin-top: 10px;
  font-size: 14px;
}
</style>